<?php
/**
 * This is a cornerstone class that provides the View(V) functionality for our implementation of MVC
 * @see http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
 */
class TemplateParser extends Engine {
	/**
	 * @var bool|null|string The path to the template directory
	 */
	public $tplsPath;
	/**
	 * @var bool|null|string The path to the project root
	 */
	public $tplsProjectPath;
	/**
	 * @var bool|null|string The path of a specific country's project root
	 */
	public $tplsCountryProjectPath;
	/**
	 * @var bool|null|string The extension of the template files
	 */
	public $tplsExt;
	/**
	 * @var null|string Path to the alternative template directory. A template placed in the alternative template directory
	 * is prioritized over a template with the same filename from the main template directory.
	 */
	public $tplsAltPath;
	/**
	 * @var bool|null|string The name of the default template
	 */
	public $tplsMain;
	/**
	 * @see \TemplateParser::$initEvalString;
	 * @var string Contains the PHP code of $initEvalString AND the template code, in order to be evaluated by this class
	 */
	public $lastEvalCode;
	/**
	 * @see \TemplateParser::assign()
	 * @see \TemplateParser::prepareVars()
	 * @var array An array containing the variables that need to be passed to the template. The keys are the desired
	 * variable identifiers, and the keys' values are the template variables' values
	 */
	private $tplsVars = array();
	/**
	 * @var string A string containing PHP code that is used to initialize the environment before the template is evaluated
	 */
	private $initEvalString = '';
	/**
	 * @var string The name of the skin folder to be used
	 */
	private $currentSkinFolder = '';

	/**
	 * Initializes the Template Parser.
	 * @param string|null $tplsPath The path to the template directory. Optional. Defaults to <b>null</b>.
	 * If not specified, the value of the TEMPLATES_PATH constant will be used
	 * @param string|null $tplsProjectPath The path to the project root directory. Optional. Defaults to <b>null</b>.
	 * If not specified, the value of the PROJECT_TEMPLATES_PATH constant will be used
	 * @param string|null $tplsCountryProjectPath The path of a specific country's project root. Optional. Defaults to <b>
	 * null</b>. If not specified, the value of the COUNTRY_PROJECT_TEMPLATES_PATH constant will be used
	 * @param string|null $tplsAltPath The path to an alternative template directory. Optional. Defaults to <b>null</b>.
	 * If not specified, the value of the TEMPLATES_ALT_PATH constant will be used. This path is used when the controller
	 * is located in a directory other than '/page/'.
	 * @param string|null $tplsMain The name of the default template. Optional. Defaults to <b>null</b>.
	 * If not specified, the value of the TEMPLATES_DEFAULT_CONTAINER constant will be used
	 * @param string|null $tplsExt The extension of the template files. Optional. Defaults to <b>null</b>.
	 * If not specified, the value of the TEMPLATES_EXTENSION constant will be used
	 * @used-by \Engine::boot()
	 * @see \TEMPLATES_PATH
	 * @see \PROJECT_TEMPLATES_PATH
	 * @see \COUNTRY_PROJECT_TEMPLATES_PATH
	 * @see \TEMPLATES_ALT_PATH
	 * @see \TEMPLATES_DEFAULT_CONTAINER
	 * @see \TEMPLATES_EXTENSION
	 */
	function __construct($tplsPath = NULL, $tplsProjectPath = NULL, $tplsCountryProjectPath = NULL, $tplsAltPath = NULL, $tplsMain = NULL, $tplsExt = NULL) {
		$this->tplsPath = ($tplsPath ? $tplsPath : (defined('TEMPLATES_PATH') ? TEMPLATES_PATH : false));
		$this->tplsProjectPath = ($tplsProjectPath ? $tplsProjectPath : (defined('PROJECT_TEMPLATES_PATH') ? PROJECT_TEMPLATES_PATH : false));
		$this->tplsCountryProjectPath = ($tplsCountryProjectPath ? $tplsCountryProjectPath : (defined('COUNTRY_PROJECT_TEMPLATES_PATH') ? COUNTRY_PROJECT_TEMPLATES_PATH : false));
		$this->tplsAltPath = ($tplsAltPath ? $tplsAltPath : (defined('TEMPLATES_ALT_PATH') ? TEMPLATES_ALT_PATH : ''));
		$this->tplsMain = ($tplsMain ? $tplsMain : (defined('TEMPLATES_DEFAULT_CONTAINER') ? TEMPLATES_DEFAULT_CONTAINER : false));
		$this->tplsExt = ($tplsExt ? $tplsExt : (defined('TEMPLATES_EXTENSION') ? TEMPLATES_EXTENSION : false));
	}

	/**
	 * Send a variable to template
	 * @param string $tplVarName The name of the variable, by which it will be accessed by the template
	 * @param mixed $tplVar The value to be assigned to the template variable
	 * @return bool True on success, false on failure(i.e. the $tplVarName is empty)
	 */
	public function assign($tplVarName = '', &$tplVar) {
		if ($tplVarName) {
			$this->tplsVars[$tplVarName] =&$tplVar;
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Checks for all possible file paths in order to locate the template. If it fails an error will be triggered.
	 * If at least one of $this->tplsPath, $this->tplsProjectPath, $this->tplsCountryProjectPath or $this->tplsExt is not
	 * defined an error will be triggered.
	 * @used-by \TemplateParser::display($tplName,$returnContent)
	 * @param null|string $filename The name of the template. Optional. If null will locate the main template
	 * @param bool $useMainTemplate Whether to use the main(default) template is the first argument is empty. Optional.
	 * Default to true. If the first argument is empty and $useMainTemplate evaluates to false an error will be triggered.
	 * @return string The absolute path to the template
	 */
	protected function locateFilePath($filename = null, $useMainTemplate = true) {
		if (!$this->tplsPath || !$this->tplsProjectPath || !$this->tplsCountryProjectPath || !$this->tplsExt) {
			trigger_error('Templates paths and extension were not defined', E_USER_ERROR);
			exit;
		}
		if (empty($filename)) {
			if ($useMainTemplate && !empty($this->tplsMain)) {
				$filename = $this->tplsMain;
			} else {
				trigger_error('Empty template name', E_USER_ERROR);
				exit;
			}
		}
		if (empty($this->tplsAltPath)) {
			$this->tplsAltPath = (defined('TEMPLATES_ALT_PATH') ? TEMPLATES_ALT_PATH : '');
		}
		if (file_exists($this->tplsCountryProjectPath.$this->tplsAltPath.$filename.$this->tplsExt)) {
			return $this->tplsCountryProjectPath.$this->tplsAltPath.$filename.$this->tplsExt;
		} elseif (file_exists($this->tplsCountryProjectPath.$filename.$this->tplsExt)) {
			return $this->tplsCountryProjectPath.$filename.$this->tplsExt;
		} elseif (file_exists($this->tplsProjectPath.$this->tplsAltPath.$filename.$this->tplsExt)) {
			return $this->tplsProjectPath.$this->tplsAltPath.$filename.$this->tplsExt;
		} elseif (file_exists($this->tplsProjectPath.$filename.$this->tplsExt)) {
			return $this->tplsProjectPath.$filename.$this->tplsExt;
		} elseif (file_exists($this->tplsPath.$filename.$this->tplsExt)) {
			return $this->tplsPath.$filename.$this->tplsExt;
		}
		trigger_error('Template file not found: '.$filename.$this->tplsExt, E_USER_ERROR);
		exit;
	}

	/**
	 * Sets the body template
	 * @param string $bodyFileName The name of the body template.
	 * @return bool True on success(the template was set), false on failure(i.e. the template file name is empty)
	 */
	public function setBodyTemplate($bodyFileName) {
		if (empty($bodyFileName)) {
			return false;
		} else {
			$this->assign('bodyTpl', $bodyFileName);
			return true;
		}
	}

	/**
	 * Sets the body class
	 * @param string $bodyClass The css body class
	 * @return bool True on success(the class was set), false on failure(i.e. the class name is empty)
	 */
	public function setBodyClass($bodyClass) {
		if (empty($bodyClass)) {
			return false;
		} else {
			$this->assign('bodyClass', $bodyClass);
			return true;
		}
	}

	/**
	 * Passes the assigned variables to the template. It does so by generating a PHP code that declares the assigned
	 * variables using their desired name and binds them by reference to the corresponding values
	 * @see $this->assign($name,$value)
	 * @uses $this->initEvalString
	 * @uses $this->tplsVars
	 */
	private function prepareVars() {
		$this->initEvalString = '';
		if (count($this->tplsVars) > 0) {
			foreach ($this->tplsVars as $varName => $varValue) {
				$this->initEvalString .= '$'.$varName.' = &$this->tplsVars[\''.$varName.'\'];'."\n";
			}
		}
		$this->initEvalString .= '?>';
	}

	/**
	 * Includes a template. Unlike the display($tplName,$returnContent) function, this one actually checks if the template
	 * name is not empty
	 * @param string $tplName The name of the template
	 * @param bool $returnContent Whether to echo or to return the template. True---return, False---echo. Optional.
	 * Defaults to false.
	 * @return bool|string False if the template name is empty. True is $returnContent is false. HTML if $returnContent
	 * is true and the template rendered successfully
	 */
	private function includeTemplate($tplName = '', $returnContent = false) {
		if (!empty($tplName)) {
			if ($returnContent) {
				return $this->display($tplName, $returnContent);
			} else {
				$this->display($tplName, $returnContent);
				return true;
			}
		}
		return false;
	}

	/**
	 * Sets the current skin folder, passes the value to the template and save it in $this->currentSkinFolder
	 * @uses \Engine::$PROJECT
	 * @see \Db_Projects::setCurrentProject()
	 */
	private function setCurrentSkin() {
		$this->assign('currentSkinFolder', self::$PROJECT['skin']['key']);
		$this->currentSkinFolder = self::$PROJECT['skin']['key'];
		$this->cssImgPath =  STATIC_SKINS_FOLDER . '/' . $this->currentSkinFolder . '/pics';
		$this->assign('cssImgPath', $this->cssImgPath);
	}

	/**
	 * Includes the skin CSS files and the user-specified CSS files
	 * @see \TemplateParser::appendCssFile()
	 * @uses \TemplateParser::$PROJECT
	 */
	private function includeCssList() {
		$this->assign('generalCssList', self::$PROJECT['general_skin_files']['css']);
		$this->assign('cssList', self::$PROJECT['skin']['files']['css']);
		$this->assign('addCssList', self::$PROJECT['general_skin_files']['css_add']);
		$this->includeTemplate('header_css');
	}

	/**
	 * Allows the inclusion of a CSS file for the template
	 * @see http://www.w3.org/TR/css3-mediaqueries/
	 * @see \TemplateParser::appendJsFile()
	 * @param string $filename The name of the css file without extension if $specialContent is false. You can also
	 * specify a relative path, i.e. <code>appendCssFile('admin/products');</code> If $specialContent is true, this
	 * parameter can contain a special-case HTML block, i.e.
	 * <code>
	 * 		&amp;lt;style type="text/css"&amp;gt;
	 * 			.myclass{padding:0 0 20px 0}
	 * 			// ...
	 *		&amp;lt;/style&amp;gt;
	 * </code>
	 * @param int $isStatic Whether the file is global for all the projects sharing the same pr_theme id. This kind
	 * of files must be placed in the 'static' folder. Optional. Defaults to true.
	 * @param string $media Allows the usage of media queries. Optional. Defaults to 'all'
	 * @param int $specialContent Whether you want to include a stylesheet by file name or by direct a HTML input.
	 * Optional. Defaults to false
	 */
	public function appendCssFile($filename, $isStatic = 1, $media = 'all', $specialContent = 0) {
		self::$PROJECT['general_skin_files']['css_add'][] = array(
			'name' => $filename,
			'special_content' => $specialContent,
			'media' => $media,
			'static' => $isStatic
		);
	}

	/**
	 * Includes the skin JS files and the user-specified JS files
	 * @see \TemplateParser::appendJsFile()
	 * @uses \TemplateParser::$PROJECT
	 */
	private function includeJsList() {
		$this->assign('generalJsList', self::$PROJECT['general_skin_files']['js']);
		$this->assign('jsList', self::$PROJECT['skin']['files']['js']);
		$this->assign('addJsList', self::$PROJECT['general_skin_files']['js_add']);
		$this->includeTemplate('header_js');
	}

	/**
	 * Allows the inclusion of a JS file for the template
	 * @see \TemplateParser::apendCssFile()
	 * @param string $filename The name of the js file, without extension. You can also specify a relative path, i.e.
	 * <code>appendCssFile('admin/products');</code> If $specialContent is true, this parameter can contain a
	 * special-case HTML block, i.e.
	 * <code>
	 * 		&amp;lt;script type="text/javascript" src="http://scripts.google.com/somescript.js"&amp;gt;&amp;lt;/script&amp;gt;
	 * 		&amp;lt;script type="text/javascript"&amp;gt;
	 * 			function myFunction() {
	 * 				//...
	 * 			}
	 * 		&amp;lt;/script&amp;gt;
	 * </code>
	 * @param int $isStatic Whether the file is global for all the projects sharing the same pr_theme id. This kind
	 * of files must be placed in the 'static' folder. Optional. Defaults to true.
	 * @param int $specialContent Whether you want to include a script by file name or by direct a HTML input.
	 * Optional. Defaults to false
	 */
	public function appendJsFile($filename, $isStatic = 1, $specialContent = 0) {
		self::$PROJECT['general_skin_files']['js_add'][] = array(
			'name' => $filename,
			'special_content' => $specialContent,
			'static' => $isStatic
		);
	}

	/**
	 * Returns or echoes the HTML of a template
	 * @param string $tplName The name of a template, without extension
	 * @param bool $returnContent Whether to return of echo the template. True---return, False---echo. Defaults to false.
	 * @return bool|string True on echo, HTML on return.
	 */
	public function display($tplName = '', $returnContent = false) {
		$displayTpl = $this->locateFilePath($tplName);
		$this->prepareVars();
		$this->lastEvalCode = $this->initEvalString.file_get_contents($displayTpl);
		//exit('<pre>'.htmlspecialchars($this->lastEvalCode).'</pre>');
		if ($returnContent) {
			ob_start();
			eval($this->lastEvalCode);
			$content = ob_get_contents();
			ob_end_clean();
			return $content;
		} else {
			eval($this->lastEvalCode);
		}
		return true;
	}
}
